﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows.Forms;
using PMC.Interfaces;
using PMC.Plugins;
using PMC.Scripts;

namespace PMC.Structs
{
    public interface IHandleable
    {
        int Interval { get; }
        HtmlDocumentHandler Handle(IWbrowser webBrowser);
    }

    [DataContractAttribute]
    public class Website : IHandleable, IComparable<Website>, ISaveloadable
    {
        private static List<Website> websites;

        public static List<Website> Websites
        {
            get
            {
                if (websites == null)
                {
                    if (Directory.Exists(_path))
                    {
                        var files = Directory.GetFiles(_path, "*.xml");
                        websites = new List<Website>(files.Length);
                        foreach (var file in files) //BUG if a website is not welformed it will throw an error!
                        {
                            var current = Path.GetFullPath(file);
                            var website = Load(current);
                            websites.Add(website);
                        }
                    }
                    // Directory not found, happens on first startup
                    else
                    {
                        websites = new List<Website>();
                    }
                }
                return websites;
            }
            set { websites = value; }
        }


        //private static Dictionary<string, Website> websites;
        //public static IEnumerable<Website> Websites
        //{
        //    get
        //    {
        //        if (websites == null)
        //        {
        //            if (!Directory.Exists(_path))
        //                throw new DirectoryNotFoundException(_path);

        //            var files = Directory.GetFiles(_path, "*.xml");
        //            websites = new Dictionary<string, Website>(files.Length);
        //            foreach (var file in files) //BUG if a website is not welformed it will throw an error!
        //            {
        //                var current = Path.GetFullPath(file);
        //                var website = Load(current);
        //                websites.Add(website.Name, website);
        //            }
        //        }
        //        return websites.Values.AsEnumerable();
        //    }
        //}

        public static void Add(string name, Uri webpage, string username, string password, Script script)
        {
            var website = new Website(name, webpage, username, password, script);
            if (websites.Any(c => c.name == website.Name))
                throw new DuplicateNameException<Website>("website with the same name exist", null, website);

            websites.Add(website);
        }

        protected Website(string name, Uri webpage, string username, string password, Script script)
        {
            Name = name;
            Webpage = webpage;
            Username = username;
            Password = password;
            Script = script;
        }

        public static Website WebsiteByUrl(Uri website)
        {
            return  (
                        from w
                        in websites
                        let notworldwidehost = String.Join(string.Empty, w.Webpage.Host.Split(new[] {"www.", "http:", "https:", "/", @"\"}, StringSplitOptions.None))
                        where w.Webpage.IsBaseOf(website) || website.OriginalString.IndexOf(notworldwidehost, StringComparison.OrdinalIgnoreCase) > -1
                        select w
                    ).FirstOrDefault();
        }

        public static void SaveAll()
        {
            foreach (var website in new List<Website>(Websites)) //Todo this is for the possiblity to delete websites from the list while iterating
            {
                if (website.Changed)
                    website.Save();
            }
        }

        public static void DiscardChanges()
        {
            Websites = null;
            //foreach (var website in Websites)
            //{
            //    website.Reload();
            //}
        }

        private string name;

        [DataMember]
        public String Name
        {
            get { return name; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException("value");
                if (websites.Any(c => c.name == value))
                    throw new DuplicateNameException<Website>("name", null, this);

                if (name != null && deletationFilename == null)
                {
                    deletationFilename = Filename;
                    changeActions.Push(w => { File.Delete(w.deletationFilename); w.deletationFilename = null; }); //Todo make this nice and uniform, for now it works (valid name before checking!)                    
                }
                Changed = true;
                name = Global.MakeValidFileName(value);
            }
        }

        private Uri webpage;

        [DataMember]
        public Uri Webpage
        {
            get { return webpage; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                Changed = true;
                webpage = value;
            }
        }

        private string username;

        [DataMember]
        public String Username
        {
            get { return username; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException("value");

                Changed = true;
                username = value;
            }
        }

        private string password;
        public String Password
        {
            get { return password; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException("value");

                Changed = true;
                password = value;
            }
        }

        //Not safe but ok
        [DataMember(Name = "Password")]
        private String serializePassword
        {
            get { return Global.EncryptString(Password); }
            set { Password = Global.DecryptString(value); }
        }

        private Script _script;
        public Script Script
        {
            get { return _script; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                Changed = true;
                _script = value;
            }
        }

        [DataMember(Name = "Script")]
        private string serializeScript
        {
            get { return Script.GetType().ToString(); }
            set { Script = Plugin<Script>.FindPlugin(value).Instance; }
        }
        private static string _path = Properties.Settings.Default.WebsiteFolder;
        private string _filename = "{0}.xml";
        public string Filename
        {
            get
            {
                return _path + String.Format(_filename, Name);
            }
        }

        private string deletationFilename;
        //[DataMember]
        //public string LoginpageOverride { get; set; }

        //public Website(string name, Uri webpage, string username, string password, Script script, IOverrideable overrideable = null)
        //{
        //    Name = name;
        //    Webpage = webpage;
        //    Username = username;
        //    Password = password;
        //    Script = script;

        //    if (overrideable != null && websites.ContainsKey(name))
        //    {
        //        websites[name].Delete();
        //    }
        //    websites.Add(name, this);
        //}

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            _path = Properties.Settings.Default.WebsiteFolder + @"\";
            _filename = "{0}.xml";
            changeActions = new Stack<Action<Website>>();
        }

        public int Interval
        {
            get { return Script.Interval; }
            private set { Script.Interval = value; }
        }

        public HtmlDocumentHandler Handle(IWbrowser webBrowser)
        {
            return Script.Handle(webBrowser);
        }

        public void Login(string path = null)
        {
            /*var cookies = */Script.Login(this, path);
            //NativeMethods.SetCookiesInternal(cookies, Webpage);
        }

        public bool Changed { get; protected set; }
        /// <summary>
        /// Stack that holds actions to perform when changes are committed
        /// </summary>
        private Stack<Action<Website>> changeActions = new Stack<Action<Website>>();

        public void Load()
        {
            Load(Filename);
        }

        public void Reload()
        {
            Load(Filename);
        }

        public void Save()
        {
            Changed = false;
            Global.XMLSerialize(Filename, this);
            while (changeActions.Count > 0)
            {
                changeActions.Pop().Invoke(this);
            }
        }

        public void Delete()
        {
            Changed = true;
            changeActions.Push(website =>
                                   {
                                       OnWebsiteDeleted(website);
                                       websites.Remove(website);
                                       if (File.Exists(website.Filename))
                                           File.Delete(website.Filename);
                                   });
            
        }

        public static void OnWebsiteDeleted(Website w)
        {
            if (WebsiteDeleted != null)
            {
                WebsiteDeleted(new WebsiteEventArgs{Website = w});
            }
        }

        public static event WebsiteDelegate WebsiteDeleted;

        public delegate void WebsiteDelegate(WebsiteEventArgs args);
        // todo move to nicer place
        public class WebsiteEventArgs
        {
            public Website Website { get; set; }
        }

        public static Website Load(string filename)
        {
            var xmlDeserialize = Global.XMLDeserialize<Website>(filename);
            xmlDeserialize.Changed = false;
            return xmlDeserialize;
        }

        public override string ToString()
        {
            return Name;
        }

        #region Implementation of IComparable<in Website>

        public int CompareTo(Website other)
        {
            return Name.CompareTo(other.Name);
        }

        #endregion

        public override bool Equals(object obj)
        {
            var o = obj as Website;
            if (o == null)
                return false;

            return (o.Name == Name && o.Webpage == Webpage && o.Username == Username && o.Password == Password &&
                    o.Script == Script && o.Interval == Interval);
        }
    }
}
